package com.sooth.system.auth.service;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.sooth.common.core.constant.Constants;
import com.sooth.common.core.constant.GlobalConstants;
import com.sooth.common.core.domain.model.LoginUser;
import com.sooth.common.core.exception.ServiceException;
import com.sooth.common.core.utils.ServletUtils;
import com.sooth.common.core.utils.SpringUtils;
import com.sooth.common.core.utils.ip.AddressUtils;
import com.sooth.common.log.event.LoginLogEvent;
import com.sooth.common.redis.utils.RedisUtils;
import com.sooth.common.satoken.utils.LoginHelper;
import com.sooth.system.dept.service.ISysDeptService;
import com.sooth.system.menu.service.ISysMenuService;
import com.sooth.system.post.service.ISysPostService;
import com.sooth.system.post.view.SysPostVo;
import com.sooth.system.role.service.ISysRoleDeptService;
import com.sooth.system.role.service.ISysRoleService;
import com.sooth.system.role.table.SysRoleDept;
import com.sooth.system.role.view.SysRoleDeptDto;
import com.sooth.system.role.view.SysRoleVo;
import com.sooth.system.tenant.service.ISysTenantService;
import com.sooth.system.tenant.view.SysTenantVo;
import com.sooth.system.user.service.ISysUserService;
import com.sooth.system.user.view.SysUserVo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 登录校验方法
 *
 * @author Lion Li
 */
@RequiredArgsConstructor
@Slf4j
@Service
public class SysLoginService {

    private final ISysUserService userService;
    private final ISysTenantService tenantService;
    private final ISysDeptService deptService;
    private final ISysMenuService menuService;
    private final ISysRoleService roleService;
    private final ISysPostService postService;
    private final ISysRoleDeptService roleDeptService;
    @Value("${user.password.maxRetryCount}")
    private Integer maxRetryCount;
    @Value("${user.password.lockTime}")
    private Integer lockTime;

    /**
     * 退出登录
     */
    public void logout() {
        try {
            LoginUser loginUser = LoginHelper.getLoginUser();
            recordLoginLog(loginUser.getTenantId(), loginUser.getUsername(), "0", "退出成功");
        } catch (NotLoginException ignored) {
        } finally {
            try {
                StpUtil.logout();
            } catch (NotLoginException ignored) {
            }
        }
    }

    /**
     * 记录登录信息
     *
     * @param tenantId 租户ID
     * @param username 用户名
     * @param status   状态
     * @param message  信息
     */
    public void recordLoginLog(String tenantId, String username, String status, String message) {
        HttpServletRequest request = ServletUtils.getRequest();
        UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
        String ip = ServletUtils.getClientIP(request);
        String address = AddressUtils.getRealAddressByIP(ip);
        String os = userAgent.getOs().getName();
        String browser = userAgent.getBrowser().getName();
        LoginLogEvent loginLogEvent = new LoginLogEvent();
        loginLogEvent.setLoginIp(ip);
        loginLogEvent.setLoginLocation(address);
        loginLogEvent.setOs(os);
        loginLogEvent.setBrowser(browser);
        loginLogEvent.setTenantId(tenantId);
        loginLogEvent.setUsername(username);
        loginLogEvent.setStatus(status);
        loginLogEvent.setMessage(message);
        SpringUtils.context().publishEvent(loginLogEvent);
    }


    /**
     * 构建登录用户
     */
    public LoginUser buildLoginUser(SysUserVo user) {
        LoginUser loginUser = new LoginUser();
        loginUser.setTenantId(user.getTenantId());
        loginUser.setUserId(user.getId());
        loginUser.setUsername(user.getUserName());
        loginUser.setNickname(user.getNickName());
        loginUser.setDeptId(user.getDeptId());
        List<String> permList = menuService.queryPermByUserId(user.getId());
        if (CollUtil.isNotEmpty(permList)) {
            loginUser.setPerms(permList.stream().distinct().collect(Collectors.toList()));
        }
        List<SysRoleVo> roleList = roleService.queryRoleByUserId(user.getId());
        if (CollUtil.isNotEmpty(roleList)) {
            List<Long> deptIds = getDeptIds(roleList);
            List<String> roleKeys = new ArrayList<>(roleList.size());
            List<Long> roleIds = new ArrayList<>(roleList.size());
            for (SysRoleVo roleVo : roleList) {
                roleKeys.add(roleVo.getRoleKey());
                roleIds.add(roleVo.getId());
            }
            loginUser.setRoles(roleKeys);
            loginUser.setRoleIds(roleIds);
            Integer dataScope = getDataScope(roleList);
            loginUser.setDataScope(dataScope);
            if (dataScope == 2) {
                List<Long> deptList = deptService.selectDeptIdsByParentId(user.getDeptId());
                if (CollUtil.isNotEmpty(deptList)) {
                    deptIds.addAll(deptList);
                }
            }
            loginUser.setDeptIds(deptIds.stream().distinct().collect(Collectors.toList()));
        }
        List<SysPostVo> postList = postService.queryPostByUserId(user.getId());
        if (CollUtil.isNotEmpty(postList)) {
            List<String> postKeys = new ArrayList<>(postList.size());
            List<Long> postIds = new ArrayList<>(roleList.size());
            for (SysPostVo postVo : postList) {
                postKeys.add(postVo.getPostCode());
                postIds.add(postVo.getId());
            }
            loginUser.setPosts(postKeys);
            loginUser.setPostIds(postIds);
        }
        return loginUser;
    }

    private List<Long> getDeptIds(List<SysRoleVo> roleList) {
        List<Long> roleIds = roleList.stream().map(SysRoleVo::getId).collect(Collectors.toList());
        SysRoleDeptDto roleDept = new SysRoleDeptDto();
        roleDept.setRoleIds(roleIds);
        List<SysRoleDept> roleDeptList = roleDeptService.list(roleDept);
        List<Long> deptIds = new ArrayList<>();
        if (CollUtil.isNotEmpty(roleDeptList)) {
            for (SysRoleDept sysRoleDept : roleDeptList) {
                deptIds.add(sysRoleDept.getDeptId());
            }
        }
        return deptIds;
    }

    private Integer getDataScope(List<SysRoleVo> roleList) {
        Integer dataScope = 5;
        for (SysRoleVo role : roleList) {
            Integer roleDataScope = role.getDataScope();
            if (dataScope > roleDataScope) {
                dataScope = roleDataScope;
            }
        }
        return dataScope;
    }

    /**
     * 记录登录信息
     *
     * @param userName 用户名称
     */
    public void recordLoginInfo(String userName) {
        userService.updateLoginInfo(userName, ServletUtils.getClientIP());
    }

    /**
     * 登录校验
     */
    public void checkLogin(String userName, Supplier<Boolean> supplier) {
        String errorKey = GlobalConstants.PWD_ERR_CNT_KEY + userName;
        // 获取用户登录错误次数，默认为0 (可自定义限制策略 例如: key + username + ip)
        int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);
        // 锁定时间内登录 则踢出
        if (errorNumber >= maxRetryCount) {
            throw new ServiceException("登录错误次数超过最大允许错误次数，已被锁定");
        }
        if (supplier.get()) {
            // 错误次数递增
            errorNumber++;
            RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
            throw new ServiceException("用户名或密码错误");

        }

        // 登录成功 清空错误次数
        RedisUtils.deleteObject(errorKey);
    }

    /**
     * 校验租户
     *
     * @param tenantId 租户ID
     */
    public void checkTenant(String tenantId) {
        if (Constants.DEFAULT_TENANT_ID.equals(tenantId)) {
            return;
        }
        SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
        if (ObjectUtil.isNull(tenant)) {
            log.info("登录租户：{} 不存在.", tenantId);
            throw new ServiceException("登录租户不存在");
        } else if (ObjectUtil.isNotNull(tenant.getExpireTime())
                && new Date().after(tenant.getExpireTime())) {
            log.info("登录租户：{} 已超过有效期.", tenantId);
            throw new ServiceException("登录租户已超过有效期");
        }
    }

}
